/*
 * Copyright (C) 2011-2013 Eric Lasota
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include <stdio.h>

#include "rdx_objectmanagement.hpp"
#include "rdx_ilcomp.hpp"

using namespace RDX::ObjectManagement;
using namespace RDX::Programmability;
using namespace RDX::ILCompiler;
using namespace RDX::ILOpcodes;

namespace RDX
{
	namespace SourceExport
	{
		static void OutLI(FILE *f, const char *argParam, LargeInt li)
		{
			fprintf(f, "%s = \"%i\", ", argParam, li);
		}

		static void OutRes(FILE *f, const char *argParam, const void *p, const Method *method)
		{
			LargeInt index = 0;
			const void *const*resArgs = method->resArgs;
			while(*resArgs != p)
			{
				index++;
				resArgs++;
			}
			OutLI(f, argParam, index);
		}

		static void OutBytes(FILE *f, const char *varName, const void *ptr, LargeUInt nBytes)
		{
			const UInt8 *b = static_cast<const UInt8 *>(ptr);
			fprintf(f, "%s = { ", varName);
			while(nBytes)
			{
				fprintf(f, "%i, ", *b);
				b++;
				nBytes--;
			}
			fprintf(f, "}, ");
		}

		void ExportSource(IObjectManager *objm, void *pf, ObjectManagement::Domain domain)
		{
			FILE *f = static_cast<FILE *>(pf);
			
			fprintf(f, "setSizes(\"%i\", \"%i\", \"%i\", \"%i\", \"%i\", \"%i\", \"%i\", \"%i\")\n", sizeof(LargeInt), sizeof(RuntimePointer<void>), sizeof(TypedRuntimePointer), RDX_ALIGNOF(void *), sizeof(Bool), RDX_ALIGNOF(Bool), sizeof(RuntimeStackValue), sizeof(EnumValue));
			
			for(GCInfo *gci = objm->FirstLiveObject(); gci; gci = objm->NextLiveObject(gci))
			{
				if(gci->containerType != objm->GetBuiltIns()->st_Method)
					continue;

				if(gci->domain != domain
#ifdef RDX_ALLOW_PRECOMPILED_DUPLICATES
					&& gci->domain != DOMAIN_Duplicable
#endif
					)
					continue;

				Method *method = reinterpret_cast<Method *>(&gci->_objectData);
				const Char *methodName = GCInfo::From(method)->gstSymbol->AsChars();

				const ILCompiler::ILInstruction *instructions = method->_native.ilinstructions;

				if(instructions)
				{
					LargeInt numInstr = GCInfo::From(instructions)->numElements;

					fprintf(f, "compileMethod [[return {\n");
					fprintf(f, "\tframeCapacity = \"%i\",\n", method->_native.frameCapacity);
					fprintf(f, "\tname = \"");
					const Char *nameChars = gci->gstSymbol->AsChars();
					for(LargeInt i=0;nameChars[i];i++)
						fputc(static_cast<int>(nameChars[i]), f);
					fprintf(f, "\",\n");

					fprintf(f, "\tinstructions = {\n");

					for(LargeInt i=0;i<numInstr;i++)
					{
						const ILInstruction *ili = instructions + i;

						fprintf(f, "\t\t{ opcode = \"%s\", ", RDX::Intrinsics::OpcodeNames[ili->opcode]);

						switch(ili->opcode)
						{
						case ILOP_debuginfo:
							{
								const Char *filenameChars = static_cast<const String *>(ili->arg.ca[0].p)->AsChars();
								if(filenameChars)
								{
									fprintf(f, "filename = \"");
									for(LargeInt j=0;filenameChars[j];j++)
										fputc(static_cast<int>(filenameChars[j]), f);
									fprintf(f, "\", ");
								}
							}
							OutLI(f, "line", ili->arg.ca[1].li);
							break;
						case ILOP_move:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutLI(f, "dest", ili->arg.ca[1].li);
							OutLI(f, "size", ili->arg.ca[2].li);
							OutLI(f, "align", ili->arg.ca[3].li);
							
							if(ili->arg.ca[4].li & ILOP_MOVE_SRC_TYPE_DEFAULT)
								OutRes(f, "srcType", ili->arg.ca[5].p, method);

							{
								LargeInt flags = ili->arg.ca[4].li;
								fputs("flags = { ", f);
								if(flags & ILOP_MOVE_DEST_PARENT_FRAME)
									fputs("destParentFrame = true, ", f);
								if(flags & ILOP_MOVE_DEST_DEREF)
									fputs("destDeref = true, ", f);
								if(flags & ILOP_MOVE_SRC_PARENT_FRAME)
									fputs("srcParentFrame = true, ", f);
								if(flags & ILOP_MOVE_SRC_DEREF)
									fputs("srcDeref = true, ", f);
								if(flags & ILOP_MOVE_SRC_TRANSIENT)
									fputs("transient = true, ", f);
								if(flags & ILOP_MOVE_SRC_TYPE_DEFAULT)
									fputs("srcTypeDefault = true, ", f);
								if(flags & ILOP_MOVE_SRC_OBJECT)
									fputs("object = true, ", f);
								fputs("}, ", f);
							}
							break;
						case ILOP_tovarying:
							OutLI(f, "dest", ili->arg.ca[0].li);
							OutRes(f, "type", ili->arg.ca[1].p, method);
							OutLI(f, "src", ili->arg.ca[2].li);
							break;
						case ILOP_tovarying_static:
							OutLI(f, "loc", ili->arg.ca[0].li);
							OutRes(f, "type", ili->arg.ca[1].p, method);
							break;
						case ILOP_pinl:
							OutLI(f, "dest", ili->arg.ca[0].li);
							OutLI(f, "offset", ili->arg.ca[1].li);
							break;
						case ILOP_pinp:
							OutLI(f, "dest", ili->arg.ca[0].li);
							OutLI(f, "offset", ili->arg.ca[1].li);
							break;
						case ILOP_incptr:
							OutLI(f, "loc", ili->arg.ca[0].li);
							OutLI(f, "offset", ili->arg.ca[1].li);
							break;

						case ILOP_objinterior:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutLI(f, "dest", ili->arg.ca[1].li);
							OutLI(f, "offset", ili->arg.ca[2].li);
							break;
						case ILOP_objinterior_notnull:
						case ILOP_objinterior_notnull_persist:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutLI(f, "dest", ili->arg.ca[1].li);
							OutLI(f, "offset", ili->arg.ca[2].li);
							break;
						case ILOP_immediate:
							OutBytes(f, "value", &ili->arg.la[0], ili->arg.la[1].ca[1].lui);
							OutLI(f, "dest", ili->arg.la[1].ca[0].li);
							OutLI(f, "size", ili->arg.la[1].ca[1].li);
							break;
						case ILOP_immediate_ptr:
							OutLI(f, "dest", ili->arg.la[1].ca[0].li);
							OutRes(f, "res", ili->arg.la[0].ca[0].p, method);
							break;
						case ILOP_immediate_rtp:
							OutLI(f, "dest", ili->arg.la[1].ca[0].li);
							OutRes(f, "res", ili->arg.la[0].rtp.objectRef, method);
							OutLI(f, "resOffset", static_cast<const UInt8 *>(ili->arg.la[0].rtp.valueRef) - static_cast<const UInt8 *>(ili->arg.la[0].rtp.objectRef));
							break;
						case ILOP_arrayindex:
							OutLI(f, "indexsrc", ili->arg.ca[0].li);
							OutLI(f, "arraysrc", ili->arg.ca[1].li);
							OutLI(f, "dest", ili->arg.ca[2].li);
							OutLI(f, "offset", ili->arg.ca[3].li);
							break;
						
						case ILOP_call:
							OutLI(f, "frameoffs", ili->arg.ca[0].li);
							OutLI(f, "prvoffs", ili->arg.ca[1].li);
							OutRes(f, "method", ili->arg.ca[2].p, method);
							fprintf(f, "isnative = %s, ", static_cast<const Method *>(ili->arg.ca[2].p)->_native.isNativeCall ? "true" : "false");
							break;
						case ILOP_calldelegatebp:
						case ILOP_calldelegateprv:
							OutLI(f, "frameoffs", ili->arg.ca[0].li);
							OutLI(f, "prvoffs", ili->arg.ca[1].li);
							OutLI(f, "methodsrc", ili->arg.ca[2].li);
							break;
						case ILOP_callvirtual:
							OutLI(f, "frameoffs", ili->arg.ca[0].li);
							OutLI(f, "prvoffs", ili->arg.ca[1].li);
							OutLI(f, "objsrc", ili->arg.ca[2].li);
							OutLI(f, "vstindex", ili->arg.ca[3].li);
							break;
						case ILOP_callinterface:
							OutLI(f, "frameoffs", ili->arg.ca[0].li);
							OutLI(f, "prvoffs", ili->arg.ca[1].li);
							OutLI(f, "objsrc", ili->arg.ca[2].li);
							OutLI(f, "vstindex", ili->arg.ca[3].li);
							OutRes(f, "method", ili->arg.ca[5].p, method);
							break;
						case ILOP_verifynotnull:
							OutLI(f, "loc", ili->arg.ca[0].li);
							break;
						case ILOP_zero:
							OutLI(f, "loc", ili->arg.ca[0].li);
							OutLI(f, "size", ili->arg.ca[1].li);
							break;
						case ILOP_newinstance:
							OutLI(f, "loc", ili->arg.ca[0].li);
							OutRes(f, "type", ili->arg.ca[1].p, method);
							OutLI(f, "dimsrc", ili->arg.ca[2].li);
							OutLI(f, "ndim", ili->arg.ca[3].li);
							OutLI(f, "stride", ili->arg.ca[4].li);
							break;
						case ILOP_exit:
							break;

						case ILOP_jump:
							OutLI(f, "instrnum", ili->arg.ca[0].li);
							break;
						case ILOP_jtrue:
							OutLI(f, "instrnum", ili->arg.ca[0].li);
							OutLI(f, "src", ili->arg.ca[1].li);
							break;
						case ILOP_jfalse:
							OutLI(f, "instrnum", ili->arg.ca[0].li);
							OutLI(f, "src", ili->arg.ca[1].li);
							break;
						case ILOP_jinherits:
							OutLI(f, "instrnum", ili->arg.ca[0].li);
							OutLI(f, "src", ili->arg.ca[1].li);
							OutRes(f, "exType", ili->arg.ca[2].p, method);
							break;

						case ILOP_tick:
							break;
						case ILOP_assertenum:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutRes(f, "type", ili->arg.ca[1].p, method);
							break;
						case ILOP_assertinherits:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutRes(f, "type", ili->arg.ca[1].p, method);
							break;
						case ILOP_hash_f:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutLI(f, "dest", ili->arg.ca[1].li);
							OutLI(f, "size", ili->arg.ca[2].li);
							OutLI(f, "align", ili->arg.ca[3].li);
							break;
						case ILOP_hash_p:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutLI(f, "dest", ili->arg.ca[1].li);
							OutLI(f, "size", ili->arg.ca[2].li);
							OutLI(f, "align", ili->arg.ca[3].li);
							break;
					
						case ILOP_jeq_f:
						case ILOP_jeq_p:
						case ILOP_jne_f:
						case ILOP_jne_p:
							OutLI(f, "instrnum", ili->arg.ca[0].li);
							OutLI(f, "src1", ili->arg.ca[1].li);
							OutLI(f, "src2", ili->arg.ca[2].li);
							OutLI(f, "size", ili->arg.ca[3].li);
							OutLI(f, "align", ili->arg.ca[4].li);
							break;

						case ILOP_xnullref:
							break;
						case ILOP_catch:
							break;
						case ILOP_fatal:
							break;
						case ILOP_throw:
							OutLI(f, "src", ili->arg.ca[0].li); break;

						case ILOP_hardenstack:
							break;

						case ILOP_switch:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutRes(f, "cases", ili->arg.ca[1].p, method);
							OutLI(f, "numCases", ili->arg.ca[2].li);
							OutLI(f, "size", ili->arg.ca[3].li);
							OutLI(f, "align", ili->arg.ca[4].li);
							break;

						case ILOP_switch_ptr:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutRes(f, "cases", ili->arg.ca[1].p, method);
							OutLI(f, "numCases", ili->arg.ca[2].li);
							OutLI(f, "size", ili->arg.ca[3].li);
							OutLI(f, "align", ili->arg.ca[4].li);
							break;
							
						case ILOP_iteratearray:
							OutLI(f, "array", ili->arg.ca[0].li);
							OutLI(f, "index", ili->arg.ca[1].li);
							OutLI(f, "dest", ili->arg.ca[2].li);
							OutLI(f, "exitInstr", ili->arg.ca[3].li);
							OutLI(f, "size", ili->arg.ca[4].li);
							OutLI(f, "align", ili->arg.ca[5].li);
							break;
							
						case ILOP_iteratearraysub:
							OutLI(f, "array", ili->arg.ca[0].li);
							OutLI(f, "index", ili->arg.ca[1].li);
							OutLI(f, "dest", ili->arg.ca[2].li);
							OutLI(f, "exitInstr", ili->arg.ca[3].li);
							OutLI(f, "size", ili->arg.ca[4].li);
							OutLI(f, "align", ili->arg.ca[5].li);
							OutLI(f, "subidxloc", ili->arg.ca[6].li);
							OutLI(f, "subidxcount", ili->arg.ca[7].li);
							break;

						default:
							OutLI(f, "src", ili->arg.ca[0].li);
							OutLI(f, "dest", ili->arg.ca[1].li);
							OutLI(f, "p2", ili->arg.ca[2].li);
							OutLI(f, "p3", ili->arg.ca[3].li);
							break;
						}
					
						fprintf(f, "},\n");
					}
					fprintf(f, "\t},\n");
					fprintf(f, "} ]]\n");
				}
			}
		}
	}
}
